#!/usr/bin/env python
#
# The MIT License
#
# Copyright (c) 2008 Nicholas Galbreath
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

"""
S3 Helpers
Progress Monitor Functions for boto.s3

http://boto.googlecode.com

v1.0

MIT License
Copyright (C) 2008 Nick Galbreath

"""

import time
import sys

def secsToString(secs):
    """
    Converts 'number of seconds' to a XX:YY format
    """
    try:
        isecs = int(secs)
        if isecs < 6000:
            # less than 100 minutes
            return '%02d:%02d' % divmod(isecs, 60)
        if isecs < 360000:
            # less than 100 hours
            return '%02d:%02d' % divmod(isecs,3600)
        if isecs < 864000:
            # less than 10 days
            return '%1ddays' % (isecs // 86400)
        return "weeks"        
    except:
        return "--:--"

def fracToString(frac, unit):
    """
    Formats a byte amount with units
    """
    fstr = "%.1f%s" % (frac, unit)
    return "%6s" % fstr

def bytesToString(bytes):
    """
    Convert raw number of bytes into human-friendly units (K,M,G)
    with a fixed width
    """
    # probably a more elegant way of doing this but it's so simple
    #   who cares.


    if bytes < (1 << 10):
        # less than 1k
        return "%6s " % str(bytes)
    if bytes < (1 << 20):
        frac = bytes / float(1 << 10)
        return fracToString(frac, 'K')
    if bytes < (1 << 30):
        frac =  bytes / float(1 << 20)
        return fracToString(frac, 'M')
    if bytes < (1 << 40):
        frac =  bytes / float(1 << 30)
        return fracToString(frac, 'G')
    #if bytes < (1 << 50):
    frac =  bytes / float(1 << 40)
    return fracToString(frac, 'T')

class ProgressMonitor:

    def __init__(self, name, window_size=20, refresh=1.0):
        self.last_bytes = 0
        self.start_time = time.time()
        self.last_time = self.start_time
        self.name = name

        # how often to update
        if refresh < 0.1:
            refresh = 0.1
        self.refresh = refresh

        if window_size < 2:
            window_size = 2
        self.window = [ [self.start_time, 0] ]
        self.window_size = window_size
        
    def update(self, got, total, now=None):
        """
        Call back for boto.s3 functions

        got is number of bytes recieved
        total is total number of bytes
        now is the current time, defaults to 'now', mostly used for QA
        """

        if now is None: now = time.time()

        if got == total:
            # all done, so totals and averages
            percent = 100
            total_time = now - self.start_time
            bytes_per_second =  total / total_time
            etas = secsToString(total_time)
            sys.stderr.write("%-30s %3d%% %10s %10sB/s %10s    \n" % (self.name, percent, bytesToString(total), bytesToString(bytes_per_second), etas))
        else:

            # ignore if we are called too frequently
            if now - self.window[-1][0]  < self.refresh:
                return

            percent = int(float(got)/float(total) * 100)
            
            if len(self.window) ==  self.window_size:
                # remove oldest entry
                self.window.pop(0)
            # add new entry
            self.window.append([now, got])

            time_delta = float(self.window[-1][0] - self.window[0][0])
            byte_delta = float(self.window[-1][1] - self.window[0][1])
            remaining_bytes = total - got
        
            if abs(time_delta) < 0.01:
                # prevent divide by 0
                bytes_per_second = 0
                etas = "--:--"
            else:
                bytes_per_second = byte_delta / time_delta
                etas = secsToString(remaining_bytes / bytes_per_second)

            # name, percent complete, received bytes, bytes/second, eta
            sys.stderr.write("%-30s %3d%% %10s %10sB/s %10s ETA\r" % (self.name, percent, bytesToString(got), bytesToString(bytes_per_second), etas))

        self.last_time = now
        self.last_bytes = got
        self.total = total


if __name__ == '__main__':
    
    total = 1234567
    p = ProgressMonitor("foobar")
    p.update(0, total)
    time.sleep(1)
    p.update(total/ 10.0, total)
    time.sleep(1)
    p.update(total, total)

    for i in xrange(0, 2000, 100):
        print "%10s %s" % (i, bytesToString(i))

    for i in xrange(30):
        print "%10s %s" % ( 1<<i, bytesToString(1<<i))
